// Code file original author: Martin Lapierre, http://devinstinct.com

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Validation;
using EntLibContrib.Validation.Validators;

namespace EntLibContrib.Validation
{
    /// <summary>
    /// The exception that is thrown when the value of one 
    /// of the arguments provided to a method is not valid.
    /// </summary>
    [Serializable]
    public class ArgumentValidationException
        : ArgumentException
    {
        #region Fields

        /// <summary>
        /// The parameter that causes this exception.
        /// </summary>
        /// <remarks>
        /// Not serialized accross application domains.
        /// </remarks>
        [NonSerialized]
        private object param;

        /// <summary>
        /// The string value of the parameter that causes this exception.
        /// </summary>
        private string paramString;

        /// <summary>
        /// The validation results of the parameter that causes this exception.
        /// </summary>
        /// <remarks>
        /// Not serialized accross application domains.
        /// </remarks>
        [NonSerialized]
        private ValidationResults validationResults;

        /// <summary>
        /// The validation messages of the parameter that causes this exception.
        /// </summary>
        private string[] validationMessages;

        /// <summary>
        /// Indicates if the exception is safe; that is, if it 
        /// contains the parameter value (unsafe) or not (safe).
        /// </summary>
        private bool isSafe = true;

        /// <summary>
        /// Provides constants used by this class.
        /// </summary>
        private class Names
        {
            public const string IsSafe = "isSafe";
            public const string ParamString = "paramString";
            public const string ValidationMessages = "validationMessages";
        }

        #endregion Fields


        #region Constructors

        /// <summary>
        /// Standard default constructor.
        /// </summary>
        public ArgumentValidationException()
        {
        }

        /// <summary>
        /// Standard constructor.
        /// </summary>
        /// <param name="message">The error message that explains the reason for the exception.</param>
        public ArgumentValidationException(string message)
            : base(message)
        {
        }

        /// <summary>
        /// Standard constructor.
        /// </summary>
        /// <param name="message">The error message that explains the reason for the exception.</param>
        /// <param name="innerException">
        /// The exception that is the cause of the current exception. If the innerException
        /// parameter is not a null reference, the current exception is raised in a catch
        /// block that handles the inner exception.
        ///</param>
        public ArgumentValidationException(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        /// <summary>
        /// Standard constructor.
        /// </summary>
        /// <param name="info">The object that holds the serialized object data.</param>
        /// <param name="context">The contextual information about the source or destination.</param>
        protected ArgumentValidationException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            // Validate pre-conditions.
            // Assumed to be validated by the base class constructor.

            this.isSafe = info.GetBoolean(Names.IsSafe);
            if (!this.isSafe)
                this.paramString = info.GetString(Names.ParamString);
            this.validationMessages = (string[])info.GetValue(Names.ValidationMessages, typeof(string[]));
        }

        /// <summary>
        /// This constructor creates a layer-safe exception that doesn't 
        /// propagate the parameter value and validation results data.
        /// </summary>
        /// <param name="message">The error message that explains the reason for the exception.</param>
        /// <param name="paramName">The name of the parameter that causes this exception.</param>
        /// <param name="validationResults">The validation results of the parameter that causes this exception.</param>
        public ArgumentValidationException(string message, string paramName, ValidationResults validationResults)
            : base(message, paramName)
        {
            // Validate pre-conditions.
            // "message" can be null.
            ArgumentValidation.Validate("paramName", paramName, DefaultValidators.NotNullValidator);
            ArgumentValidation.Validate("validationResults", validationResults, DefaultValidators.NotNullValidator);

            InitValidationMessages(validationResults);
        }

        /// <summary>
        /// This constructor creates a layer-unsafe exception that 
        /// propagates the parameter value and validation results data.
        /// </summary>
        /// <param name="message">The error message that explains the reason for the exception.</param>
        /// <param name="paramName">The name of the parameter that causes this exception.</param>
        /// <param name="param">The parameter that causes this exception.</param>
        /// <param name="validationResults">The validation results of the parameter that causes this exception.</param>
        public ArgumentValidationException(string message, string paramName, object param, ValidationResults validationResults)
            : base(message, paramName)
        {
            // Validate pre-conditions.
            // "message" can be null.
            // "param" can be null.
            ArgumentValidation.Validate("paramName", paramName, DefaultValidators.NotNullValidator);
            ArgumentValidation.Validate("validationResults", validationResults, DefaultValidators.NotNullValidator);

            this.isSafe = false;
            this.param = param;
            this.paramString = param == null ? null : param.ToString();
            this.validationResults = validationResults;
            InitValidationMessages(validationResults);
        }

        #endregion Constructors


        #region Properties

        /// <summary>
        /// The parameter that causes this exception.
        /// </summary>
        /// <remarks>
        /// Can be null.
        /// Not serialized.
        /// </remarks>
        public object Param
        {
            get { return this.param; }
        }

        /// <summary>
        /// The string value of the parameter that causes this exception.
        /// </summary>
        /// <remarks>
        /// Can be null.
        /// </remarks>
        public string ParamString
        {
            get { return this.paramString; }
        }

        /// <summary>
        /// The validation results of the parameter that causes this exception.
        /// </summary>
        /// <remarks>
        /// Can be null.
        /// Not serialized.
        /// </remarks>
        public ValidationResults ValidationResults
        {
            get { return this.validationResults; }
        }

        /// <summary>
        /// The validation messages of the parameter that causes this exception.
        /// </summary>
        /// <remarks>
        /// Can be null.
        /// </remarks>
        public string[] ValidationMessages
        {
            get { return this.validationMessages; }
        }

        /// <summary>
        /// Gets a value which indicates if the exception is safe; that is, 
        /// if it contains the parameter value (unsafe) or not (safe).
        /// </summary>
        public bool IsSafe
        {
            get { return this.isSafe; }
        }

        /// <summary>
        /// Gets the error message, including the parameter 
        /// name and the validation messages, if available.
        /// </summary>
        /// <remarks>
        /// Can't be null.
        /// </remarks>
        public override string Message
        {
            get
            {
                StringBuilder message = new StringBuilder(base.Message);

                if (!this.isSafe)
                {
                    message.Append(Environment.NewLine);
                    message.AppendFormat(Resources.ArgumentValidationExceptionParameterValue, this.paramString ?? "(null)");
                }

                if (this.validationMessages != null)
                    foreach (string validationMessage in this.validationMessages)
                    {
                        message.Append(Environment.NewLine);
                        message.Append(validationMessage);
                    }

                return message.ToString();
            }
        }

        #endregion Properties


        #region Methods

        /// <summary>
        /// Initializes the validation messages from the validation results.
        /// </summary>
        /// <param name="validationResults">The validation results of the parameter that causes this exception.</param>
        private void InitValidationMessages(ValidationResults validationResults)
        {
            // Validate pre-conditions.
            // Internal class call: arguments assumed to be valid.

            List<string> resultMessages = new List<string>();
            foreach (ValidationResult result in validationResults)
                resultMessages.Add(result.Message);
            this.validationMessages = resultMessages.ToArray();
        }

        /// <summary>
        /// Sets the <see cref="SerializationInfo"/> object with 
        /// the parameter name and additional exception information.
        /// </summary>
        /// <param name="info">The object that holds the serialized object data.</param>
        /// <param name="context">The contextual information about the source or destination.</param>
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            // Validate pre-conditions.
            // Assumed to be validated by call to base class.

            base.GetObjectData(info, context);

            info.AddValue(Names.IsSafe, this.isSafe);
            if (!this.isSafe)
                info.AddValue(Names.ParamString, this.paramString);
            info.AddValue(Names.ValidationMessages, this.validationMessages);
        }

        #endregion Methods
    }
}
